package ru.romannep.pc.chopperbattle;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Iterator;


/**
 * Класс реализует управление игрой.
 *
 *
 */
public class ChopperBattleGameEngine {

    public ArrayList<GameElement> borders = new ArrayList<GameElement>();
    public ArrayList<GameElement> objects = new ArrayList<GameElement>(); //все объекты на поле, для проверки столкновений
    public ArrayList<DestroyAnim> destroy = new ArrayList<DestroyAnim>();
    public static final float FIELD_SIZE_X = 640;
    public static final float FIELD_SIZE_Y = 380;
    private static final float FIELD_BORDER_SIZE = 20;
    public static final float BORDER_ELAST = 1f;
    private static final float GAME_TIME = 1;
    public static final int GAME_CYCLES_PER_SECOND = 20;
    public static final float CHOPPER_SIZE_X = 32f;
    public static final float CHOPPER_SIZE_Y = 22f;
    public static final float CHOPPER_CRITICAL_SPEED = 3f;
    public static final float ACC_GRAVITY = 0.2f;
    public static final float ACC_CHOPPER_X = 0.2f;
    public static final float ACC_CHOPPER_Y = 0.4f;
    public static final float MAX_SPEED = 4f;
    public static final float BULLET_SIZE = 1f;
    public static final int MAX_BULLETS_COUNT = 8;
    public static final float FUEL_CAPACITY = 20f;
    public static final float FUEL_USE_IDLE = 0.005f;
    public static final float FUEL_USE_ENGINE = 0.02f;
    public static final float FUEL_FILL = 1.0f;
    public static final int MAX_DROPS = 8;
    public static final int DESTROY_ANIM_LENGTH = 5;
    public boolean gameRunning;
    public GameChopper chopper1, chopper2;
    public ArrayList<GameBullet> bullets = new ArrayList<GameBullet>(); //пули
    public int chopper1drops, chopper2drops;
    public String winnerText = "";
    public StartPlaces startPlaces;
    private ArrayList<GameChopperCommand> chopper1Commands = new ArrayList<GameChopperCommand>();
    private ArrayList<GameChopperCommand> chopper2Commands = new ArrayList<GameChopperCommand>();
    long gameCycle;
    //Сетевая игра
    public boolean networkGame = false; //флаг
    private ArrayList<ChopperCommands> commands = new ArrayList<ChopperCommands>(); //набор команд для отправки
    public static int CYCLES_TO_SEND = 10; //количество игровых циклов без синхоронизации
    private ChopperBattleConnectionToServer connection; //соединение с сервером
    //Нужны синхронизированные (между игрогами, с сервером) игровые объекты и данные - для этого создадим копию движка.
    private ChopperBattleGameEngine geSyn;
    //Наборы команд для синхронизации
    private ArrayList<ChopperCommands> chopper1CommandsSyn = new ArrayList<ChopperCommands>();
    private ArrayList<ChopperCommands> chopper2CommandsSyn = new ArrayList<ChopperCommands>();
   

    public ChopperBattleGameEngine(ChopperBattleConnectionToServer connection) {
        this.connection = connection;
        networkGame = true;

        chopper1 = new GameChopper(this, 0, 0, true);
        chopper1.reset();
        objects.add(chopper1);
        chopper2 = new GameChopper(this, 0, 0, true);
        chopper2.reset();
        objects.add(chopper2);

        geSyn = new ChopperBattleGameEngine();
        
        gameRunning = false;

    }

    public ChopperBattleGameEngine() {

/*
         borders.add(new GameElement(0, -FIELD_BORDER_SIZE, FIELD_SIZE_X,FIELD_BORDER_SIZE, BORDER_ELAST)); //top 
         borders.add(new GameElement(0, FIELD_SIZE_Y, FIELD_SIZE_X, FIELD_BORDER_SIZE,BORDER_ELAST)); //bottom 
         borders.add(new GameElement(-FIELD_BORDER_SIZE, 0, FIELD_BORDER_SIZE, FIELD_SIZE_Y,BORDER_ELAST)); //left 
         borders.add(new GameElement(FIELD_SIZE_X, 0,FIELD_BORDER_SIZE, FIELD_SIZE_Y, BORDER_ELAST)); //right
         
          borders.add(new GameElement(0, 32, 24, 380, BORDER_ELAST));
          borders.add(new GameElement(616, 32, 24, 380, BORDER_ELAST));
          borders.add(new GameElement(191, 191, 256, 30, BORDER_ELAST));
          borders.add(new GameElement(225, 255, 190, 30, BORDER_ELAST));
          borders.add(new GameElement(288, 127, 63, 31, BORDER_ELAST));
          borders.add(new GameElement(0, 351, FIELD_SIZE_X, FIELD_BORDER_SIZE,
          BORDER_ELAST));
         
          borders.add(new GameElement(204, 34, 75, 60, BORDER_ELAST));
          borders.add(new GameElement(193, 43, 11, 44, BORDER_ELAST));
          borders.add(new GameElement(193 + 83, 43, 11, 44, BORDER_ELAST));
          borders.add(new GameElement(GameElementTypes.FUEL_STATION, 213, 32, 58, 2,BORDER_ELAST));
         
          borders.add(new GameElement(204 + 159, 34, 75, 60, BORDER_ELAST));
          borders.add(new GameElement(193 + 159, 43, 11, 44, BORDER_ELAST));
          borders.add(new GameElement(193 + 83 + 159, 43, 11, 44, BORDER_ELAST)); 
          borders.add(new GameElement(GameElementTypes.FUEL_STATION,213 + 159, 32, 58, 2,BORDER_ELAST));
         
          borders.add(new GameElement(24, 44, 7, 42, BORDER_ELAST));
          borders.add(new GameElement(24, 107, 7, 42, BORDER_ELAST));
          borders.add(new GameElement(24, 172, 7, 42, BORDER_ELAST));
          borders.add(new GameElement(24, 235, 7, 42, BORDER_ELAST));
          borders.add(new GameElement(24, 300, 7, 60, BORDER_ELAST));
         
          borders.add(new GameElement(609, 44, 7, 42, BORDER_ELAST));
          borders.add(new GameElement(609, 107, 7, 42, BORDER_ELAST));
          borders.add(new GameElement(609, 172, 7, 42, BORDER_ELAST));
          borders.add(new GameElement(609, 235, 7, 42, BORDER_ELAST));
          borders.add(new GameElement(609, 300, 7, 60, BORDER_ELAST));

        objects.addAll(borders);

          
          startPlaces = new StartPlaces(2); startPlaces.places[0][0] = 10;
          startPlaces.places[0][1] = 5; startPlaces.places[1][0] = 605;
          startPlaces.places[1][1] = 5;
*/



        chopper1 = new GameChopper(this, 0, 0, true);
        chopper1.reset();
        objects.add(chopper1);
        chopper2 = new GameChopper(this, 0, 0, true);
        chopper2.reset();
        objects.add(chopper2);

        gameRunning = false;

    }

    /**
     * Запускает новую игру
     */
    public void gameStart() {
        chopper1drops = 0;
        chopper2drops = 0;
        gameRunning = true;
        winnerText = "";
        chopper1.reset();
        chopper2.reset();

        //если вдруг остались пули с прошлой игры - удалим
        for (GameBullet bullet : bullets) {
            bullet.inGame = false;
        }
        Iterator<GameBullet> i = bullets.iterator();
        while (i.hasNext()) {
            GameBullet bullet = i.next();
            if (!bullet.inGame) {
                i.remove();
                objects.remove(bullet);
            }
        }

        destroy.clear();
        gameCycle = 0;
        
        if (networkGame) {
            geSyn.gameStart();
        }

    }

    /**
     * Выполняет игровой цикл.
     */
    public void gameCycle() {
        
        if (!gameRunning) {
            return;
        }

        if (networkGame) {
            networkGameCycle();
            return;
        }
        
        //обрабатываем команды интерфейса к вертолетам
        ArrayList<GameChopperCommand> chopper1CommandsCopy;
        ArrayList<GameChopperCommand> chopper2CommandsCopy;

        synchronized (chopper1Commands) {
            chopper1CommandsCopy = new ArrayList<GameChopperCommand>(chopper1Commands);
            chopper1Commands.clear();
        }
        synchronized (chopper2Commands) {
            chopper2CommandsCopy = new ArrayList<GameChopperCommand>(chopper2Commands);
            chopper2Commands.clear();
        }
        //обработаем команды вертолетам
        for (GameChopperCommand command : chopper1CommandsCopy) {
            processChopperCommand(chopper1, command);
        }
        for (GameChopperCommand command : chopper2CommandsCopy) {
            processChopperCommand(chopper2, command);
        }

        
        doGameCycle();
        
        //увеличиваем на 1 кадр анимации взрыва если они есть
        Iterator<DestroyAnim> d = destroy.iterator();
        while (d.hasNext()) {
            DestroyAnim da = d.next();
            da.count--;
            if (da.count < 0) {
                d.remove();
            }
        }

        //если счет зашкалил - отображаем кто победил
        if (chopper1drops >= MAX_DROPS) {
            gameRunning = false;
            winnerText = "!!! Player 2 WIN !!!";
        }
        if (chopper2drops >= MAX_DROPS) {
            gameRunning = false;
            winnerText = "!!! Player 1 WIN !!!";
        }

        gameCycle++;

    }

    /**
     * Выполняет основные действия игрового цикла.
     * 
     * @param chopper1GameCommands
     * @param chopper2GameCommands 
     */
    private void doGameCycle() {


        //удаляем вышедшие из игры пули
        Iterator<GameBullet> i = bullets.iterator();
        while (i.hasNext()) {
            GameBullet bullet = i.next();
            if (!bullet.inGame) {
                i.remove();
                objects.remove(bullet);
            }
        }

        //двигаем пули
        for (GameBullet bullet : bullets) {
            bullet.move(GAME_TIME, objects);
        }

        //двигаем вертолеты
        if (chopper1.inGame) {
            chopper1.move(GAME_TIME, objects);
        }
        if (chopper2.inGame) {
            chopper2.move(GAME_TIME, objects);
        }
        
        
    }
    
    
    /**
     * Выполняет игровой цикл при сетевой игре.
     *
     *
     */
    private void networkGameCycle() {

        //соберем команды локального игрока
        ArrayList<GameChopperCommand> chopper1CommandsCopy;

        synchronized (chopper1Commands) {
            chopper1CommandsCopy = new ArrayList<GameChopperCommand>(chopper1Commands);
            chopper1Commands.clear();
        }

        ChopperCommands currentCommand = new ChopperCommands(gameCycle, chopper1CommandsCopy);
        chopper1CommandsSyn.add(currentCommand);
        
        //отправим, если пора, команды на сервер
        commands.add(currentCommand);

        if (commands.size() >= CYCLES_TO_SEND) {
            connection.sendGameData(commands);
            commands.clear();
        }


        //соберем команды удаленного игрока
        synchronized (connection.chopperCommands) {
            if (connection.chopperCommands.size() > 0) {
                chopper2CommandsSyn.addAll(connection.chopperCommands);
                connection.chopperCommands.clear();
            }
        }

        //просчитаем синхронзированную позицию игры
        doSyncrhonizedGameCycles();
        
        //синхронизируем объекты
        synchronizeObjects();
        
        //просчитаем прогноз игры
        predictGame();

        gameCycle++;
    }
    
    /**
     * Выполняет синхронизированный просчет игры. 
     * Выполняет столько игровых циклов, сколько есть данных.
     * 
     */
    private void doSyncrhonizedGameCycles(){
        
        //тут тупо глупо идем от gameCyleSyn до мин из двух списков команд Syn
        //попутно чистим списки Syn

        if ( (chopper1CommandsSyn.size()==0) || (chopper2CommandsSyn.size()==0) ) {
            return; //нечего считать
        }
        
        Iterator<ChopperCommands> i1 = chopper1CommandsSyn.iterator();
        Iterator<ChopperCommands> i2 = chopper2CommandsSyn.iterator();
        while (i1.hasNext() && i2.hasNext()) {
            ChopperCommands chopper1Command = i1.next();
            ChopperCommands chopper2Command = i2.next();
            
            i1.remove();
            i2.remove();
            
            //ошибки синхронизации - напишем и игнорим
            if (chopper1Command.gameCycle != geSyn.gameCycle) {
                System.out.println("Err chop 1 command sync: gC " + geSyn.gameCycle + " c1C.gC " + chopper1Command.gameCycle);
            }
            if (chopper2Command.gameCycle != geSyn.gameCycle) {
                System.out.println("Err chop 2 command sync: gC " + geSyn.gameCycle + " c2C.gC " + chopper2Command.gameCycle);
            }
            
            //установим команды
            for (GameChopperCommand command : chopper1Command.commands) {
                geSyn.processChopperCommand(geSyn.chopper1, command);
            }
            for (GameChopperCommand command : chopper2Command.commands) {
                geSyn.processChopperCommand(geSyn.chopper2, command);
            }
            
            //выполним игровой цикл
            geSyn.gameCycle();
            
        }
        
    }
    
    /**
     * Корректирует игровые объекты на основании синхронизованных.
     * 
     */
    private void synchronizeObjects(){
        
        //тут меняем позиции, скорости, ускорения у вертолетов и пуль
        //обновляем коллекции
        chopper1.synchronize(geSyn.chopper1);
        chopper2.synchronize(geSyn.chopper2);

        for (GameBullet bullet: bullets) {
            objects.remove(bullet);
        }
        bullets.clear();
        
        for (GameBullet bullet: geSyn.bullets) {
            GameBullet newBullet = new GameBullet(chopper2, bullet.x, bullet.y, bullet.speedX, bullet.speedY);
            objects.add(newBullet);
            bullets.add(newBullet);
        }
        
        startPlaces.current = geSyn.startPlaces.current;
    }
    /**
     * Предсказывает положение игры до текущего игрового цикла.
     * 
     * Считает, что все игровые объекты находятся на игровом цикле geSyn.gameCycle,
     * используя имеющиеся команды (если есть) предсказывает их положение до gameCycle
     */
    private void predictGame(){
        
        //объекты у нас находятся на стадии geSyn.gameCycle, а игра - на gameCycle
        //используя имеющиеся команды подвинем объекты
        
        Iterator<ChopperCommands> i1 = chopper1CommandsSyn.iterator();
        Iterator<ChopperCommands> i2 = chopper2CommandsSyn.iterator();

        for (long i = geSyn.gameCycle; i <= gameCycle; i++) {
            if (i1.hasNext()){
                ChopperCommands chopper1Command = i1.next();
                if (chopper1Command.gameCycle != i) {
                    System.out.println("(predict) Err chop 1 command sync: gC " + i + " c1C.gC " + chopper1Command.gameCycle);
                }
                for (GameChopperCommand command : chopper1Command.commands) {
                    processChopperCommand(chopper1, command);
                }
                
            }
            
            if (i2.hasNext()) { //по идее удаленные команды не могут идти вперед, но вдруг? :)
                ChopperCommands chopper2Command = i2.next();
                if (chopper2Command.gameCycle != i) {
                    System.out.println("(predict) Err chop 2 command sync: gC " + i + " c1C.gC " + chopper2Command.gameCycle);
                }
                for (GameChopperCommand command : chopper2Command.commands) {
                    processChopperCommand(chopper2, command);
                }
                
                
            }
            
            doGameCycle();
            
        }
        
    }

    /**
     * Убивает вертолет переданный в качетве параметра
     *
     * @param obj
     */
    public void destroyChopper(GameChopper obj) {
        obj.inGame = false;
        //добавляем анимацию взрыва
        destroy.add(new DestroyAnim((int) obj.x, (int) obj.y, DESTROY_ANIM_LENGTH));
        obj.reset();
        //меняем счет
        if (obj == chopper1) {
            chopper1drops++;
        }
        if (obj == chopper2) {
            chopper2drops++;
        }
    }

    /**
     * Добавляет в игру пулю переданную в качестве параметра
     *
     * @param obj
     */
    public void addBullet(GameBullet obj, GameChopper chopper) {
//        //newBullets.add(obj);
//        if (chopper == chopper1 || chopper == chopper2) {
            bullets.add(obj);
            objects.add(obj);
//
//        } else {
//            bulletsSyn.add(obj);
//            objectsSyn.add(obj);
//        }
    }

    /**
     * передает команду вертолету, если он в игре, а если нет, то по команде
     * ОГОНЬ вводит его в игру.
     *
     * @param chopper
     * @param command
     */
    private void processChopperCommand(GameChopper chopper, GameChopperCommand command) {

        if (chopper.inGame) {
            chopper.processCommands(command);
        } else {
            if (command == GameChopperCommand.FIRE) {
                respawnChopper(chopper, objects, startPlaces);
            }
       }

    }
//    private void processChopperCommandSyn(GameChopper chopper, GameChopperCommand command) {
//
//        if (chopper.inGame) {
//            chopper.processCommands(command);
//        } else {
//            respawnChopper(chopper, objectsSyn, startPlacesSyn);
//       }
//
//    }
    
    /**
     * Вводит вертолет в игру.
     * 
     * @param chopper
     * @param objs
     * @param startPlaces 
     */
    private void respawnChopper(GameChopper chopper, ArrayList<GameElement> objs, StartPlaces startPlaces){
        
        chopper.inGame = true;
        int place = startPlaces.getCurrent();
        chopper.x = startPlaces.places[place][0];
        chopper.y = startPlaces.places[place][1];
        GameElement intersectedElement = chopper.intersectedElement(objs);
        if ((intersectedElement != null) && (intersectedElement.type == GameElementTypes.CHOPPER)) {
            place = startPlaces.getCurrent();
            chopper.x = startPlaces.places[place][0];
            chopper.y = startPlaces.places[place][1];
        }

        
    }
            

    /**
     * Принимает от интерфейса команду вертолету для последующей обработки.
     *
     * @param chopper
     * @param command
     */
    public void chopperCommand(GameChopper chopper, GameChopperCommand command) {

        //System.out.println(command);
        //Надо сказать, что тут есть один нюанс - повторение:
        //винда генерит keyPressed события при удерживании клавиши нажатой.
        if (chopper == chopper1) {
            chopper1Commands.add(command);
        } else if (chopper == chopper2) {
            chopper2Commands.add(command);
        }

    }

    /**
     * Сохраняет игровые элементы(границы и стартовые места) в файл, переданный
     * в качестве параметра
     *
     * @param fileName
     * @throws IOException
     */
    public void saveLevel(String fileName) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(fileName));
        out.writeObject("Level data");
        out.writeObject(startPlaces);
        out.writeObject(borders);
        out.close();

    }

    /**
     * Загружает игровые элементы(границы и стартовые места) из ресурся,
     * переданного в качестве параметра.
     *
     * @param resName
     */
    public void loadLevel(String resName) {
        borders.clear();
        objects.clear();
        startPlaces = null;
        try {
            ObjectInputStream in = new ObjectInputStream(this.getClass().getResourceAsStream(resName));
            String levelName = (String) in.readObject();
            startPlaces = (StartPlaces) in.readObject();
            borders = (ArrayList<GameElement>) in.readObject();

        } catch (ClassNotFoundException ex) {
        } catch (IOException ex) {
        }
        objects.addAll(borders);
        objects.add(chopper1);
        objects.add(chopper2);
        
        if (networkGame) {
            geSyn.loadLevel(resName);
        }

    }
    public void loadLevel(InputStream resStream) {
        borders.clear();
        objects.clear();
        startPlaces = null;
        try {
            ObjectInputStream in = new ObjectInputStream(resStream);
            String levelName = (String) in.readObject();
            startPlaces = (StartPlaces) in.readObject();
            borders = (ArrayList<GameElement>) in.readObject();

        } catch (ClassNotFoundException ex) {
        } catch (IOException ex) {
        }
        objects.addAll(borders);
        objects.add(chopper1);
        objects.add(chopper2);
        
        if (networkGame) {
            geSyn.loadLevel(resStream);
        }

    }

    
//    public static GameEngine getGameEngine() {
//        if (ge == null) {
//            ge = new GameEngine();
//        }
//        return ge;
//    }
}
